package message;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.Console;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.util.LinkedList;

import util.Point;

/**
 * This class offers methods for different IO functions.
 * It offers getting messages from the command line and
 * saving & reading messages and ciphers to file.
 * @author Ralf
 *
 */
public class FileIO {
	
	/**
	 * Reads a file and stores it formatted in a String.
	 * @param filename the name of the file to be read with extension
	 * @return the content of the file
	 * @throws IOException is thrown, when a reading error occurs
	 */
	public static String readMessageFromFile(String filename) throws IOException {		
		FileReader reader = new FileReader(filename);
		BufferedReader in = new BufferedReader(reader);
		
		String message = new String();
		String input  = new String();
		try {
			while ((input = in.readLine()) != null) {
				message += input + "\n";
			}
		} catch (IOException e) {
			in.close();
		}
			
		return message;
	}
	
	/**
	 * Reads a full text from stdin, until a single line "quit" is entered.
	 * @return the entered text as a String
	 * @throws IOException is thrown, when a reading error occurs
	 */
	public static String readMessageFromPrompt() throws IOException {
		InputStreamReader reader = new InputStreamReader(System.in);
		BufferedReader in = new BufferedReader(reader);
		
		System.out.println("Please enter your message. End message with single line of \"quit\"");
		String input = new String();
		String message = new String();		
		
		try {
			while (!(input = in.readLine()).equals("quit")) {
				message += input + "\n";
			}
		} catch (IOException e) {
			in.close();
		}
		
		return message;
	}
	
	/**
	 * Reads one single line from stdin, querying a passphrase. 
	 * @return the entered line
	 * @throws IOException is thrown, when a reading error occurs
	 */
	public static String readLineFromPrompt(String outputMessage) throws IOException {
		System.out.print(outputMessage);
		String passphrase = new String();
		
		InputStreamReader reader = new InputStreamReader(System.in);
		BufferedReader in = new BufferedReader(reader);

		try {
			passphrase = in.readLine();
		} catch (IOException e) {
			in.close();
		}
		
		return passphrase;
	}
	
	
	/**
	 * This method reads a password from the console hiding the input.
	 * 
	 * Warning: Does not work, when started within eclipse.
	 * @return the entered password or an empty string, if no console is found.
	 */
	String readPassword() {
		Console console = System.console();
		if (console == null) {
			System.err.println("No console found.");
			return new String();
		}
		System.out.println("Please enter your password: ");
		String password = new String(console.readPassword());
		
		return password;
	}	
	
	/**
	 * Writes a simple String unencrypted to a file.
	 * @param message the message to be saved
	 * @param filename the filename with extension
	 * @throws IOException is thrown, when a writing error occurs
	 */
	public static void writeMessageToFile(String message, String filename) throws IOException {
		FileWriter writer = new FileWriter(filename);
		BufferedWriter out = new BufferedWriter(writer);
		try {
			out.write(message);
		} finally {
			out.close();
		}
	}
	
	/**
	 * Writes a cipher into a file.
	 * @param cipher the cipher to be saved
	 * @param filename the filename with extension
	 * @throws IOException is thrown, when a writing error occurs
	 */
	public static void writeCipherToFile(Cipher cipher, String filename) throws IOException {
		FileWriter writer = new FileWriter(filename);
		BufferedWriter out = new BufferedWriter(writer);
		try {
			out.write(cipher.getK().toString() + "\n");
			out.write(cipher.getPublicKey().toString() + "\n");
			LinkedList<Point> encryptedMessagePoints = cipher.getEncryptedPoints();
			LinkedList<Point> signatures = cipher.getSignatures();
			
			for (int i = 0; i < encryptedMessagePoints.size(); i++) {
				out.write(encryptedMessagePoints.get(i).toString());
				if (signatures != null && signatures.size() != 0) {
					out.write(";" + signatures.get(i).toString());
				}
				out.write("\n");
			}
		} finally {
			out.close();
		}
	}
	
	/**
	 * Reads a cipher file and stores it into local memory.
	 * @param filename the filename with extension
	 * @return a cipher containing the data found in the file
	 * @throws IOException is thrown, when some reading error occurs
	 */
	public static Cipher readCipherFromFile(String filename) throws IOException {	
		FileReader reader = new FileReader(filename);
		BufferedReader in = new BufferedReader(reader);
		
		Point publicKey = new Point();
		BigInteger k = BigInteger.ZERO;
		LinkedList<Point> encryptedMessagePoints = new LinkedList<Point>();
		LinkedList<Point> signatures = new LinkedList<Point>();
		
		String input  = new String();
		// read the public key of the sender first
		try {
			input = in.readLine();
			k = new BigInteger(input);
			input = in.readLine();
			publicKey = new Point(input);
		} catch (IOException e) {
			System.err.println(e.getLocalizedMessage());
		}
		
		boolean set = false;
		boolean signed = false;
		try {
			while ((input = in.readLine()) != null) {
				String[] points = input.split(";");
				encryptedMessagePoints.add(new Point(points[0]));
				if (points.length == 2) {
					if (!set) {
						set = true;
						signed = true;
					} else if (!signed) {
						throw new CorruptedFileException("Found signed point in unsigned file");
					}
					signatures.add(new Point(points[1]));
				} else if (!set) {
					set = true;
				} else if (signed) {
					throw new CorruptedFileException("Found unsigned point in signed file");
				}
			}
		} finally {
			in.close();
		}
		
		if (signed) {
			return new Cipher(publicKey, k, encryptedMessagePoints, signatures);
		} else {
			return new Cipher(publicKey, k, encryptedMessagePoints);
		}
	}

}
